using System;
using System.Collections;

namespace VCDepend.Engine
{
	public class DependencyGraph
	{
		private ArrayList m_nodes;

		public DependencyGraph()
		{
			m_nodes = new ArrayList();
		}

		public int NodeCount
		{
			get
			{
				return m_nodes.Count;
			}
		}

		public void AddNode(string name)
		{
			if (!HasNode(name))
				CreateAndAddNode(name);
		}

		public void AddDependency(string from, string to)
		{
			if (HasTransitiveDependency(to, from))
				throw new ArgumentException("Adding dependency between " + from + " and " + to + " would cause a cycle.");

			DependencyNode fromNode = FindOrAddNode(from);
			DependencyNode toNode = FindOrAddNode(to);

			fromNode.AddDependency(toNode);
		}

		public bool HasNode(string name)
		{
			return m_nodes.Contains(new DependencyNode(name));
		}

		public bool HasDependency(string from, string to)
		{
			DependencyNode fromNode = FindNode(from);
			if (fromNode == null)
				return false;

			DependencyNode toNode = FindNode(to);
			if (toNode == null)
				return false;

			return fromNode.HasDependency(toNode);
		}

		public void Traverse(IDependencyAction action)
		{
			ArrayList processed = new ArrayList();

			foreach (DependencyNode node in m_nodes)
			{
				TraverseNode(node, action, processed);
			}
		}

		private void TraverseNode(DependencyNode node, IDependencyAction action, IList processed)
		{
			if (processed.Contains(node.Name))
				return;

			string[] dependencies = StringArrayMaker.ToArray(node.Dependencies, new StringArrayMaker.Converter(GetNodeName));
			action.OnDependencyNode(node.Name, dependencies);
			processed.Add(node.Name);

			foreach (DependencyNode current in node.Dependencies)
			{
				TraverseNode(current, action, processed);
			}
		}

		private string GetNodeName(object node)
		{
			return ((DependencyNode)node).Name;
		}

		private bool HasTransitiveDependency(string from, string to)
		{
			DependencyNode fromNode = FindNode(from);
			if (fromNode == null)
				return false;

			DependencyNode toNode = fromNode.FindDependency(to);
			return (toNode != null);
		}

		private DependencyNode FindNode(string name)
		{
			foreach (DependencyNode project in m_nodes)
			{
				if (project.Name == name)
					return project;
			}

			return null;
		}

		private DependencyNode CreateAndAddNode(string name)
		{
			DependencyNode project = new DependencyNode(name);
			m_nodes.Add(project);

			return project;
		}

		private DependencyNode FindOrAddNode(string name)
		{
			DependencyNode node = FindNode(name);
			if (node == null)
				node = CreateAndAddNode(name);

			return node;
		}
	}
}
